Creates an object structure for a boiler, describes its mapping into OPC Data Access server using attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET object access.
The main program:
// ConsoleLiveMapping: Creates an object structure for a boiler, describes its mapping into OPC Data Access server using // attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET // object access. // // Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . using System; using System.Diagnostics; using System.Threading; using OpcLabs.BaseLib.Runtime.InteropServices; using OpcLabs.EasyOpc.DataAccess; using OpcLabs.EasyOpc.DataAccess.LiveMapping; using OpcLabs.EasyOpc.DataAccess.LiveMapping.Extensions; namespace ConsoleLiveMapping { class Program { static void Main() { ComManagement.Instance.AssureSecurityInitialization(); Console.WriteLine(); Console.WriteLine("Mapping our data structures to OPC..."); var mapper = new DAClientMapper(); var boiler1 = new Boiler(); mapper.Map(boiler1, new DAMappingContext { ServerDescriptor = "OPCLabs.KitServer.2", // local OPC server // The NodeDescriptor below determines where in the OPC address space we want to map our data to. NodeDescriptor = new DANodeDescriptor { BrowsePath = "/Boilers/Boiler #1"}, GroupParameters = 1000, // requested update rate (for subscriptions) }); Console.WriteLine(); Console.WriteLine("Reading all data of the boiler..."); mapper.Read(); Console.WriteLine($"Drum level is: {boiler1.Drum.LevelIndicator.Output}"); Console.WriteLine(); Console.WriteLine("Writing new setpoint value..."); boiler1.LevelController.SetPoint = 50.0; Debug.Assert(!(boiler1.LevelController is null)); mapper.WriteTarget(boiler1.LevelController, /*recurse:*/false); Console.WriteLine(); Console.WriteLine("Subscribing to boiler data changes..."); mapper.Subscribe(/*active:*/true); Thread.Sleep(30 * 1000); Console.WriteLine(); Console.WriteLine("Unsubscribing from boiler data changes..."); mapper.Subscribe(/*active:*/false); Console.WriteLine(); Console.WriteLine("Press Enter to continue..."); Console.ReadLine(); } } }
' ConsoleLiveMapping: Creates an object structure for a boiler, describes its mapping into OPC OPC Data Access server using ' attributes, and then performs the live mapping. Boiler data is then read, written and/or subscribed to using plain .NET ' object access. ' ' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . Imports System.Threading Imports OpcLabs.BaseLib.Runtime.InteropServices Imports OpcLabs.EasyOpc.DataAccess Imports OpcLabs.EasyOpc.DataAccess.LiveMapping Imports OpcLabs.EasyOpc.DataAccess.LiveMapping.Extensions ' ReSharper disable CheckNamespace Namespace ConsoleLiveMapping ' ReSharper restore CheckNamespace Friend Class Program <MTAThread> ' needed for COM security initialization to succeed Shared Sub Main() ComManagement.Instance.AssureSecurityInitialization() Console.WriteLine() Console.WriteLine("Mapping our data structures to OPC...") Dim mapper = New DAClientMapper() Dim boiler1 = New Boiler() mapper.Map(boiler1, New DAMappingContext With {.ServerDescriptor = "OPCLabs.KitServer.2", .NodeDescriptor = New DANodeDescriptor With {.BrowsePath = "/Boilers/Boiler #1"}, .GroupParameters = 1000}) ' requested update rate (for subscriptions) - local OPC server ' The NodeDescriptor below determines where in the OPC address space we want to map our data to. Console.WriteLine() Console.WriteLine("Reading all data of the boiler...") mapper.Read() Console.WriteLine("Drum level is: {0}", boiler1.Drum.LevelIndicator.Output) Console.WriteLine() Console.WriteLine("Writing new setpoint value...") boiler1.LevelController.SetPoint = 50.0 Debug.Assert(boiler1.LevelController IsNot Nothing) mapper.WriteTarget(boiler1.LevelController, False) 'recurse:False Console.WriteLine() Console.WriteLine("Subscribing to boiler data changes...") mapper.Subscribe(True) 'active:True Thread.Sleep(30 * 1000) Console.WriteLine() Console.WriteLine("Unsubscribing from boiler data changes...") mapper.Subscribe(False) 'active:True Console.WriteLine() Console.WriteLine("Press Enter to continue...") Console.ReadLine() End Sub End Class End Namespace
The Boiler class:
// // Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . using System; using OpcLabs.BaseLib.LiveMapping; using OpcLabs.EasyOpc.DataAccess; using OpcLabs.EasyOpc.DataAccess.LiveMapping; namespace ConsoleLiveMapping { // The Boiler and its constituents are described in our application domain terms, the way we want to work with them. // Attributes are used to describe the correspondence between our types and members, and OPC nodes. // This is how the boiler looks in OPC address space: // - Boiler #1 // - CC1001 (CustomController) // - ControlOut // - Description // - Input1 // - Input2 // - Input3 // - Drum1001 (BoilerDrum) // - LIX001 (LevelIndicator) // - Output // - FC1001 (FlowController) // - ControlOut // - Measurement // - SetPoint // - LC1001 (LevelController) // - ControlOut // - Measurement // - SetPoint // - Pipe1001 (BoilerInputPipe) // - FTX001 (FlowTransmitter) // - Output // - Pipe1002 (BoilerOutputPipe) // - FTX002 (FlowTransmitter) // - Output [DAType] class Boiler { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [DANode(BrowsePath = "Pipe1001")] public BoilerInputPipe InputPipe = new BoilerInputPipe(); [DANode(BrowsePath = "Drum1001")] public BoilerDrum Drum = new BoilerDrum(); [DANode(BrowsePath = "Pipe1002")] public BoilerOutputPipe OutputPipe = new BoilerOutputPipe(); [DANode(BrowsePath = "FC1001")] public FlowController FlowController = new FlowController(); [DANode(BrowsePath = "LC1001")] public LevelController LevelController = new LevelController(); [DANode(BrowsePath = "CC1001")] public CustomController CustomController = new CustomController(); } [DAType] class BoilerInputPipe { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [DANode(BrowsePath = "FTX001")] public FlowTransmitter FlowTransmitter1 = new FlowTransmitter(); [DANode(BrowsePath = "ValveX001")] public Valve Valve = new Valve(); } [DAType] class BoilerDrum { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [DANode(BrowsePath = "LIX001")] public LevelIndicator LevelIndicator = new LevelIndicator(); } [DAType] class BoilerOutputPipe { // Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. [DANode(BrowsePath = "FTX002")] public FlowTransmitter FlowTransmitter2 = new FlowTransmitter(); } [DAType] class FlowController : GenericController { } [DAType] class LevelController : GenericController { } [DAType] class CustomController { [DANode, DAItem] public double Input1 { get; set; } [DANode, DAItem] public double Input2 { get; set; } [DANode, DAItem] public double Input3 { get; set; } [DANode, DAItem(Operations = DAItemMappingOperations.ReadAndSubscribe)] // no OPC writing public double ControlOut { get; set; } [DANode, DAItem] public string Description { get; set; } } [DAType] class FlowTransmitter : GenericSensor { } [DAType] class Valve : GenericActuator { } [DAType] class LevelIndicator : GenericSensor { } [DAType] class GenericController { [DANode, DAItem(Operations = DAItemMappingOperations.ReadAndSubscribe)] // no OPC writing public double Measurement { get; set; } [DANode, DAItem] public double SetPoint { get; set; } [DANode, DAItem(Operations = DAItemMappingOperations.ReadAndSubscribe)] // no OPC writing public double ControlOut { get; set; } } [DAType] class GenericSensor { // Meta-members are filled in by information collected during mapping, and allow access to it later from your code. // Alternatively, you can derive your class from DAMappedNode, which will bring in many meta-members automatically. [MetaMember("NodeDescriptor")] public DANodeDescriptor NodeDescriptor { get; set; } [DANode, DAItem(Operations = DAItemMappingOperations.ReadAndSubscribe)] // no OPC writing public double Output { get => _output; set { _output = value; Console.WriteLine($"Sensor \"{NodeDescriptor}\" output is now {value}."); } } private double _output; } [DAType] class GenericActuator { [DANode, DAItem] public double Input { get; set; } } }
' ' Find all latest examples here: https://opclabs.doc-that.com/files/onlinedocs/OPCLabs-OpcStudio/Latest/examples.html . Imports OpcLabs.BaseLib.LiveMapping Imports OpcLabs.EasyOpc.DataAccess Imports OpcLabs.EasyOpc.DataAccess.LiveMapping ' ReSharper disable CheckNamespace Namespace ConsoleLiveMapping ' ReSharper restore CheckNamespace ' The Boiler and its constituents are described in our application domain terms, the way we want to work with them. ' Attributes are used to describe the correspondence between our types and members, and OPC nodes. ' This is how the boiler looks in OPC address space: ' - Boiler #1 ' - CC1001 (CustomController) ' - ControlOut ' - Description ' - Input1 ' - Input2 ' - Input3 ' - Drum1001 (BoilerDrum) ' - LIX001 (LevelIndicator) ' - Output ' - FC1001 (FlowController) ' - ControlOut ' - Measurement ' - SetPoint ' - LC1001 (LevelController) ' - ControlOut ' - Measurement ' - SetPoint ' - Pipe1001 (BoilerInputPipe) ' - FTX001 (FlowTransmitter) ' - Output ' - Pipe1002 (BoilerOutputPipe) ' - FTX002 (FlowTransmitter) ' - Output <DAType()> _ Friend Class Boiler ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <DANode(BrowsePath:="Pipe1001")> _ Public InputPipe As New BoilerInputPipe() <DANode(BrowsePath:="Drum1001")> _ Public Drum As New BoilerDrum() <DANode(BrowsePath:="Pipe1002")> _ Public OutputPipe As New BoilerOutputPipe() <DANode(BrowsePath:="FC1001")> _ Public FlowController As New FlowController() <DANode(BrowsePath:="LC1001")> _ Public LevelController As New LevelController() <DANode(BrowsePath:="CC1001")> _ Public CustomController As New CustomController() End Class <DAType()> _ Friend Class BoilerInputPipe ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <DANode(BrowsePath:="FTX001")> _ Public FlowTransmitter1 As New FlowTransmitter() <DANode(BrowsePath:="ValveX001")> _ Public Valve As New Valve() End Class <DAType()> _ Friend Class BoilerDrum ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <DANode(BrowsePath:="LIX001")> _ Public LevelIndicator As New LevelIndicator() End Class <DAType()> _ Friend Class BoilerOutputPipe ' Specifying BrowsePath-s here only because we have named the class members differently from OPC node names. <DANode(BrowsePath:="FTX002")> _ Public FlowTransmitter2 As New FlowTransmitter() End Class <DAType()> _ Friend Class FlowController Inherits GenericController End Class <DAType()> _ Friend Class LevelController Inherits GenericController End Class <DAType()> _ Friend Class CustomController <DANode(), DAItem()> _ Public Property Input1 As Double <DANode(), DAItem()> _ Public Property Input2 As Double <DANode(), DAItem()> _ Public Property Input3 As Double <DANode(), DAItem(Operations:=DAItemMappingOperations.ReadAndSubscribe)> _ Public Property ControlOut As Double <DANode(), DAItem()> _ Public Property Description As String End Class <DAType()> _ Friend Class FlowTransmitter Inherits GenericSensor End Class <DAType()> _ Friend Class Valve Inherits GenericActuator End Class <DAType()> _ Friend Class LevelIndicator Inherits GenericSensor End Class <DAType()> _ Friend Class GenericController <DANode(), DAItem(Operations:=DAItemMappingOperations.ReadAndSubscribe)> _ Public Property Measurement As Double <DANode(), DAItem()> _ Public Property SetPoint As Double <DANode(), DAItem(Operations:=DAItemMappingOperations.ReadAndSubscribe)> _ Public Property ControlOut As Double End Class <DAType()> _ Friend Class GenericSensor ' Meta-members are filled in by information collected during mapping, and allow access to it later from your code. ' Alternatively, you can derive your class from DAMappedNode, which will bring in many meta-members automatically. <MetaMember("NodeDescriptor")> _ Public Property NodeDescriptor As DANodeDescriptor <DANode(), DAItem(Operations:=DAItemMappingOperations.ReadAndSubscribe)> _ Public Property Output() As Double ' no OPC writing Get Return _output End Get Set(ByVal value As Double) _output = value Console.WriteLine("Sensor ""{0}"" output is now {1}.", NodeDescriptor, value) End Set End Property Private _output As Double End Class <DAType()> _ Friend Class GenericActuator <DANode(), DAItem()> _ Public Property Input As Double End Class End Namespace
Copyright © 2004-2024 CODE Consulting and Development, s.r.o., Plzen. All rights reserved. Web page: www.opclabs.com
Send Documentation Feedback. Resources: Knowledge Base, Product Downloads. Technical support: Online Forums, FAQ.Missing some example? Ask us for it on our Online Forums! You do not have to own a commercial license in order to use Online Forums, and we reply to every post.